//-----------------------------------------------------------------------------
// Name: DisplayManager.cpp
// Auth: Bradford J. Smith
// Desc: contains all of the interface required to manipulate the view as well
//		as any additional state information.
//-----------------------------------------------------------------------------

#include "DisplayManager.h"
#include "Assert.h"
#include "SharedConstants.h"

#include <GL/glut.h>

#include <math.h>

#include <fstream>

//-----------------------------------------------------------------------------
// Constants:
//-----------------------------------------------------------------------------
const float kInitialCameraPitch = 0.0f;
const float kInitialCameraHeading = 0.0f;
const float kInitialCameraOffsetOrtho = 2.0f;
const float kInitialCameraOffsetProjection = 30.0f;

const float kMinCameraOffset = 0.5f;

const char* kDisplaySettingsFileName = "DisplaySettings.txt";

//-----------------------------------------------------------------------------
// cDisplayManager:
//-----------------------------------------------------------------------------
cDisplayManager::cDisplayManager() :
	mWindowWidth(kInitialWindowWidth),
	mWindowHeight(kInitialWindowHeight),
	mProjectionMode(kPerspective),
	mCameraPitch(kInitialCameraPitch),
	mCameraHeading(kInitialCameraHeading),
	mCameraOffset(kInitialCameraOffsetProjection)
{
	// Start the camera looking at the origin from down the z-Axis
	for (int i = 0; i < 3; ++i)
	{
		mCameraCOI[i] = 0.0f;
	}
}

cDisplayManager::~cDisplayManager()
{

}

//-----------------------------------------------------------------------------
void cDisplayManager::Init()
{
	LoadSettings();
}

//-----------------------------------------------------------------------------
void cDisplayManager::Shutdown()
{
	SaveSettings();
}

//-----------------------------------------------------------------------------
void cDisplayManager::SetWindowArea(int width, int height)
{
	mWindowWidth = width;
	mWindowHeight = height;
}

//-----------------------------------------------------------------------------
int cDisplayManager::GetWindowWidth()
{
	return mWindowWidth;
}

//-----------------------------------------------------------------------------
int cDisplayManager::GetWindowHeight()
{
	return mWindowHeight;
}

//-----------------------------------------------------------------------------
void cDisplayManager::SetPerspectiveMode()
{
	// update the mode and then force the projection to update
	mProjectionMode = kPerspective;
	UpdateProjection();
}

//-----------------------------------------------------------------------------
void cDisplayManager::SetOrthographicMode()
{
	// update the mode and then force the projection to update
	mProjectionMode = kOrthographic;
	UpdateProjection();
}

//-----------------------------------------------------------------------------
void cDisplayManager::UpdateProjection()
{
	// we are going to modify the projection matrix
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	switch (mProjectionMode)
	{
	case kOrthographic:
		{
			// create a basic orthographic projection
			glOrtho(-40.0, 40.0, -40.0, 40.0, -40.0, 40.0);
		}
		break;
	case kPerspective:
		{
			// compute the aspect ratio, being careful to cast to float
			float aspectRatio = (float)mWindowWidth / (float)mWindowHeight;

			// create a basic perspective projection
			gluPerspective(80.0f, aspectRatio, 1.0f, 100.0f);
		}
		break;
	default:
		{
			// Unsupported mode!
			Assert(false);
		}
		break;
	}

	// reset the model view in any case.
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

//-----------------------------------------------------------------------------
void cDisplayManager::SetCameraPitch(float pitch)
{
	mCameraPitch = pitch;

	// cap pitch to avoid being directly above or below the center of interest.
	if (mCameraPitch > 89.9f)
	{
		mCameraPitch = 89.9f;
	}
	else if (mCameraPitch < -89.9f)
	{
		mCameraPitch = -89.9f;
	}
}

//-----------------------------------------------------------------------------
void cDisplayManager::SetCameraHeading(float heading)
{
	mCameraHeading = heading;

	// wrap the heading values to keep them between 0 and 360
	if (mCameraHeading > 360.0f)
	{
		mCameraHeading -= 360.0f;
	}
	else if (mCameraHeading < 0.0f)
	{
		mCameraHeading += 360.0f;
	}
}

//-----------------------------------------------------------------------------
void cDisplayManager::SetCameraOffset(float offset)
{
	if (offset < kMinCameraOffset)
	{
		mCameraOffset = kMinCameraOffset;
	}
	else 
	{
		mCameraOffset = offset;
	}
}

//-----------------------------------------------------------------------------
float cDisplayManager::GetCameraPitch()
{
	return mCameraPitch;
}

//-----------------------------------------------------------------------------
float cDisplayManager::GetCameraHeading()
{
	return mCameraHeading;
}

float cDisplayManager::GetCameraOffset()
{
	return mCameraOffset;
}

//-----------------------------------------------------------------------------
void cDisplayManager::UpdateCamera()
{
	// Test the assumptions of the camera
	Assert(mCameraOffset >= kMinCameraOffset);

	// reset the model view matrix to identity
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// convert to radians
	float pitch = mCameraPitch * kDegreesToRadians;
	float heading = mCameraHeading * kDegreesToRadians;

	// convert the polar coordinates of heading and pitch to a direction
	tVector3f direction = {sin(heading) * cos(pitch), sin(pitch), cos(heading) * cos(pitch)};

	// construct the actual camera position using the center of interest,
	// the direction, and the offset.
	tVector3f cameraPosition;
	for (int i = 0; i < 3; ++i)
	{
		cameraPosition[i] = mCameraCOI[i] + direction[i] * mCameraOffset;
	}

	// move the camera using the lookat data
	gluLookAt(cameraPosition[0], cameraPosition[1], cameraPosition[2], 
			  mCameraCOI[0], mCameraCOI[1], mCameraCOI[2], 
			  kUpVector[0], kUpVector[1], kUpVector[2]);
}

//-----------------------------------------------------------------------------
void cDisplayManager::DrawDebugAxes()
{
	// create one line for each axis
	glDisable(GL_LIGHTING);
	glDisable(GL_TEXTURE_2D);
	glColor3f(0.6f, 0.6f, 0.6f);
	glLineWidth(1.5f);
	glBegin(GL_LINES);
		glVertex3f(0.0f, -8.0f, 0.0f);
		glVertex3f(0.0f, 8.0f, 0.0f);
	glEnd();

	glLineWidth(2.5f);
	glBegin(GL_LINES);
		glVertex3f(-8.0f, 0.0f, 0.0f);
		glVertex3f(8.0f, 0.0f, 0.0f);
	glEnd();

	glLineWidth(3.5f);
	glBegin(GL_LINES);
		glVertex3f(0.0f, 0.0f, -8.0f);
		glVertex3f(0.0f, 0.0f, 8.0f);
	glEnd();

	// create a simple label for each axis in 3D
	glColor3f(0.3f, 0.3f, 0.3f);
	glDisable(GL_DEPTH_TEST);
	glRasterPos3f(9.0f, 0.0f, 0.0f);
	glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'X');
	glRasterPos3f(0.0f, 9.0f, 0.0f);
	glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'Y');
	glRasterPos3f(0.0f, 0.0f, 9.0f);
	glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'Z');

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_TEXTURE_2D);
}

void cDisplayManager::SaveSettings()
{
	std::ofstream outFile(kDisplaySettingsFileName);

	if (outFile.is_open())
	{
		// save the pitch
		outFile << mCameraPitch << std::endl;
		// save the heading
		outFile << mCameraHeading << std::endl;
		// save the offset
		outFile << mCameraOffset << std::endl;
	}

	outFile.close();
}

void cDisplayManager::LoadSettings()
{
	// open the file
	std::ifstream inFile(kDisplaySettingsFileName);

	if (inFile.is_open())
	{
		// save the pitch
		inFile >> mCameraPitch;
		// save the heading
		inFile >> mCameraHeading;
		// save the offset
		inFile >> mCameraOffset;
	}

	inFile.close();
}